package me.helllp.dake.core.crypto;

import lombok.extern.slf4j.Slf4j;
import me.helllp.dake.common.exception.*;
import me.helllp.dake.common.security.AESEnum;
import me.helllp.dake.common.security.AesCrypto;
import me.helllp.dake.common.security.CryptoUtil;
import me.helllp.dake.core.config.CryptoConfig;
import org.apache.commons.lang3.ObjectUtils;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

@Component
@Slf4j
public class SecuritySerivce {
    static {
        // 指定使用bouncycastle包来加密
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final String KEY_PART_STATIC_128 = "1DnBS7fFhqg=";
    private static final String KEY_PART_STATIC_192 = "20HDdhBr8eFwuUMb";
    private static final String KEY_PART_STATIC_258 = "MmhsItzm/DJk2yrgOBOqnQ==";

    private static final byte[] KEY_PART_128_BYTE = CryptoUtil.decode(KEY_PART_STATIC_128);
    private static final byte[] KEY_PART_192_BYTE = CryptoUtil.decode(KEY_PART_STATIC_192);
    private static final byte[] KEY_PART_256_BYTE = CryptoUtil.decode(KEY_PART_STATIC_258);

    private byte[] choose(Integer keyLength) throws DakeKeyLengthException {
        if (keyLength == 128) {
            return KEY_PART_128_BYTE;
        } else if (keyLength == 192) {
            return KEY_PART_192_BYTE;
        } else if (keyLength == 256) {
            return KEY_PART_256_BYTE;
        } else {
            throw new DakeKeyLengthException();
        }
    }

    private byte[] getKey() throws DakeKeyLengthException {
        byte[] keyPair = choose(cryptoConfig.getKeyLength());
        byte[] keys = new byte[keyPair.length + cryptoConfig.getKeyPartByte().length];
        System.arraycopy(keyPair, 0, keys, 0, keyPair.length);
        System.arraycopy(cryptoConfig.getKeyPartByte(), 0, keys, keyPair.length, cryptoConfig.getKeyPartByte().length);
        return keys;
    }

    @Autowired
    private CryptoConfig cryptoConfig;

    public String encrypt(String data) throws DakeException, UnsupportedEncodingException {
        byte[] bytes = data.getBytes("UTF-8");
        return encrypt(bytes);
    }

    public String encrypt(byte[] data) throws DakeException {
        byte[] keys = getKey();
        byte[] iv = CryptoUtil.genIv();

        if (ObjectUtils.isEmpty(cryptoConfig.getAesEnum())) {
            throw new DakeAesAlgorithmException();
        }
        byte[] endData = AesCrypto.encrypt(cryptoConfig.getAesEnum(), data, keys, iv);

        StringBuilder builder = new StringBuilder();
        builder.append(CryptoUtil.encode(iv))
                .append("&")
                .append(CryptoUtil.encode(endData));
        return builder.toString();
    }

    public String decrypt(String data) throws DakeException {
        String[] arrays = data.split("&");
        if (arrays.length != 2) {
            log.error("解密过程：加密数据有错误");
            throw new DakeDecryptException();
        }

        byte[] iv = CryptoUtil.decode(arrays[0]);
        byte[] encData = CryptoUtil.decode(arrays[1]);
        AESEnum aesEnum = AESEnum.parser(cryptoConfig.getAlgorithm());
        if (ObjectUtils.isEmpty(aesEnum)) {
            log.error("解密过程：解密算法有错误");
            throw new DakeDecryptException();
        }
        byte[] keys = getKey();
        byte[] decBytes = AesCrypto.decrypt(aesEnum, encData, keys, iv);
        return new String(decBytes);
    }

    public String sha1ToHex(String data) {
        Digest digest = new SHA1Digest();
        digest.update(data.getBytes(), 0, data.getBytes().length);
        byte[] sha1Bytes = new byte[digest.getDigestSize()];
        digest.doFinal(sha1Bytes, 0);
        return Hex.toHexString(sha1Bytes);
    }
}
